#C 语言的结构体 struct
C 语言中的结构体是一种自定义的数据类型,它允许我们将不同类型的数据组合成一个整体。结构体是 C 语言中实现复杂数据结构的基础,也是面向对象编程中"类"概念的雏形。
结构体的定义使用 struct
关键字:
// 定义结构体类型
struct 结构体类型名
{
类型 变量名1;
类型 变量名2;
类型 变量名3;
// ...
};
// 定义结构体类型的变量
struct 结构体类型名 变量名;
例如,定义一个表示玩家信息的结构体:
// 定义结构体类型 struct Player
struct Player
{
char name[64]; // 名称
int hp; // 生命值
int mp; // 魔法值
};
// 入口函数
int main(void)
{
// 定义 struct Player 类型的变量 p1 p2
struct Player p1 = {'Mario', 6, 10};
struct Player p2 = {'Luigi', 5, 15};
}
这里定义了一个名为 Player
的结构体类型,它包含名称(name
),生命值(hp
)和魔法值(mp
)三个成员。
然后定义了 struct Player
类型的变量 p1
和 p2
:
- 前者的名称为
Mario
,拥有 6 点生命值和 10 点魔法值 - 后者名为
Luigi
,拥有 5 点生命值和 15 点魔法值
#访问结构体成员
通过结构体类型的变量访问其内部成员时需要使用 .
运算符,例如:
#include <stdio.h>
// 定义结构体类型 struct Player
struct Player
{
char name[64]; // 名称
int hp; // 生命值
int mp; // 魔法值
};
int main(void)
{
struct Player p1 = {'Mario', 6, 10};
printf("name: %s\n", p1.name); // 通过 . 访问成员 name
printf("hp: %d\n", p1.hp); // 通过 . 访问成员 hp
printf("mp: %d\n", p1.mp); // 通过 . 访问成员 mp
return 0;
}
通过结构体指针类型的变量访问其内部成员时需要使用 ->
运算符,例如:
#include <stdio.h>
// 定义结构体类型 struct Player
struct Player
{
char name[64]; // 名称
int hp; // 生命值
int mp; // 魔法值
};
int main(void)
{
struct Player p1 = {'Mario', 6, 10};
struct Player* ptr = &p1; // 指针指向 p1
printf("name: %s\n", ptr->name); // 通过 -> 访问成员 name
printf("hp: %d\n", ptr->hp); // 通过 -> 访问成员 hp
printf("mp: %d\n", ptr->mp); // 通过 -> 访问成员 mp
return 0;
}
#结构体大小与内存对齐
显然,结构体的大小就是其成员大小之和。例如:
#include <stdio.h>
// 定义结构体类型 struct Player
struct Player
{
char name[64]; // 64 字节
int hp; // 4 字节
int mp; // 4 字节
};
int main(void)
{
printf("struct Player 的大小是 %zu 字节\n", sizeof(struct Player));
return 0;
}
运行结果:
struct Player 的大小是 72 字节
这里 struct Player
的大小为
但是有时候简单的相加结果却不正确。例如:
#include <stdio.h>
// 定义结构体类型 struct Player
struct Player
{
char name[65]; // 65 字节
int hp; // 4 字节
int mp; // 4 字节
};
int main(void)
{
printf("struct Player 的大小是 %zu 字节\n", sizeof(struct Player));
return 0;
}
运行结果:
struct Player 的大小是 76 字节
这里将数组 name
从 64 个 char
改为了 65 个 char
,而 struct Player
并非
这是因为这里发生了内存对齐,导致 name
和 hp
之间空了 3 个字节。
内存对齐(Memory Alignment) 是指数据在内存中的存储位置(地址)必须是某个数值的整数倍。
这样做的目的是提高硬件访问数据的性能。现代计算机硬件中的CPU在数据 自然对齐 的情况下,能够最高效地执行内存读写操作。
所谓 自然对齐 就是 N 字节的数据类型按照 N 字节对齐,即内存地址是 N 的整数倍。
上述结构体中的 hp
是 4 个字节,自然对齐即 4 字节对齐,也就是内存地址是 4 的整数倍。
假设 struct Player
的内存地址从 0 开始,那么如果不对齐的话,hp
的内存地址应当是 65。
而 4 字节对齐时,65 不是 4 的正数倍,大于 65 的 4 的最小整数倍数是 68,因此 hp
会空 3 个字节放置到地址 68 的内存位置。
#include <stdio.h>
// 定义结构体类型 struct Player
struct Player
{
char name[65]; // 65 字节
int hp; // 4 字节
int mp; // 4 字节
};
int main(void)
{
struct Player p;
printf("struct Player 的大小是 %zu 字节\n", sizeof(p));
printf("p 的地址是 %p\n", &p);
printf("p.name 的地址是 %p,偏移量为 %td\n", &p.name, (char*)&p.name - (char*)&p);
printf("p.hp 的地址是 %p,偏移量为 %td\n", &p.hp, (char*)&p.hp - (char*)&p);
printf("p.mp 的地址是 %p,偏移量为 %td\n", &p.mp, (char*)&p.mp - (char*)&p);
return 0;
}
运行结果:
struct Player 的大小是 76 字节 p 的地址是 0x7ffd69a20b90 p.name 的地址是 0x7ffd69a20b90,偏移量为 0 p.hp 的地址是 0x7ffd69a20bd4,偏移量为 68 p.mp 的地址是 0x7ffd69a20bd8,偏移量为 72
#修改对齐方式
可以使用预处理指令 #pragma pack
可以修改对齐方式。
#pragma pack(n) // 设为 n(1, 2, 4, 8, 16...)字节对齐
#pragma pack() // 设为默认的对齐方式,通常是自然对齐
#pragma pack(push) // 保存当前的对齐方式
#pragma pack(pop) // 恢复之前保存的对齐方式
例如:
#include <stdio.h>
#pragma pack(push) // 保存当前的对齐方式
#pragma pack(1) // 设为 1 字节对齐,也就是不对齐
// 定义结构体类型 struct Player
struct Player
{
char name[65]; // 65 字节
int hp; // 4 字节
int mp; // 4 字节
};
#pragma pack(pop) // 恢复之前保存的对齐方式
int main(void)
{
struct Player p;
printf("struct Player 的大小是 %zu 字节\n", sizeof(p));
printf("p 的地址是 %p\n", &p);
printf("p.name 的地址是 %p,偏移量为 %td\n", &p.name, (char*)&p.name - (char*)&p);
printf("p.hp 的地址是 %p,偏移量为 %td\n", &p.hp, (char*)&p.hp - (char*)&p);
printf("p.mp 的地址是 %p,偏移量为 %td\n", &p.mp, (char*)&p.mp - (char*)&p);
return 0;
}
运行结果:
struct Player 的大小是 73 字节 p 的地址是 0x7ffe1e9ef8c0 p.name 的地址是 0x7ffe1e9ef8c0,偏移量为 0 p.hp 的地址是 0x7ffe1e9ef901,偏移量为 65 p.mp 的地址是 0x7ffe1e9ef905,偏移量为 69
#结构体的位域
定义结构体类型时,可以在其成员后面通过添加 : bit
的方式注明该字段的位数。
这个功能常用在硬件操作、格式解析等底层开发中。例如:
// 定义 UART 的 SR 寄存器
struct UART1_Register_SR
{
int PE : 1; // 奇偶校验错误
int FE : 1; // 帧错误
int NF : 1; // 噪声标识
int OR : 1; // 溢出错误
int IDLE : 1; // 空闲
int RXNE : 1; // 接收寄存器非空
int TC : 1; // 传输完成
int TXE : 1; // 发送寄存器非空
};
这里定义了一个结构体类型用于表示 UART(通用异步收发器)的 SR(状态) 寄存器。
该结构体包含 8 个字段,每个字段都只有 1 位;不考虑内存对齐的前提下,该结构体的大小是 1 字节。